package org.erikaredmark.monkeyshines.editor;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import javax.swing.BoxLayout;
import javax.swing.JButton;
import javax.swing.JPanel;
import javax.swing.SwingConstants;
import javax.swing.plaf.basic.BasicArrowButton;
import org.erikaredmark.monkeyshines.TileMap;
import org.erikaredmark.monkeyshines.TileMap.Direction;
import org.erikaredmark.monkeyshines.World;
import org.erikaredmark.monkeyshines.background.Background;
import org.erikaredmark.monkeyshines.background.SingleColorBackground;
import org.erikaredmark.monkeyshines.editor.model.Template;
import com.google.common.base.Function;
/**
*
* Graphical interface for modifying a single template. Accepts brush updates from {@code BrushPalette} and can be re-sized
* dynamically. Accepts an initial template for construction (or none for a default 3x3 window) and when exited will return
* the new template that has been created.
*
* @author Erika Redmark
*
*/
@SuppressWarnings("serial")
public class TemplateEditor extends JPanel {
/**
*
* Creates the template editor for the given template.
*
* @param initial
* the template for initial creation
*
* @param world
* the world the template is created for
*
* @param saveAction
* called when the template is 'saved'. Passes both the original template and the template that was saved. The original
* can be used to determine which template to 'replace' for edit operations. It is possible for base template to be null
* if this editor was in a 'new template' state and not a 'modifying template' state.
*
*/
public TemplateEditor(Template initial, World world, final Function<TemplatePair, Void> saveAction) {
// Okay to be null
baseTemplate = initial;
TileMap map = initial != null
? initial.fitToTilemap()
: new TileMap(3, 3);
// Template editor main part: top. The save will take up a special bottom part
setLayout(new BorderLayout() );
primaryEditor = new JPanel(new BorderLayout() );
add(primaryEditor, BorderLayout.NORTH);
internalEditor = new MapEditor(map, new SingleColorBackground(Color.BLACK), world, true);
primaryEditor.add(internalEditor, BorderLayout.CENTER);
// Eight buttons, two per compass direction. One expands in that direction, one contracts in that direction.
// These do not modify the template. They modify the size of the map in the editor
// TOP
JPanel topAll = new JPanel();
topAll.setLayout(new BoxLayout(topAll, BoxLayout.LINE_AXIS) );
JButton topExpand = new BasicArrowButton(SwingConstants.NORTH);
topExpand.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) {
TileMap newMap = internalEditor.getTileMap().resize(1, Direction.NORTH);
replaceMap(newMap);
}
});
JButton topShrink = new BasicArrowButton(SwingConstants.SOUTH);
topShrink.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) {
TileMap map = internalEditor.getTileMap();
if (map.getRowCount() <= 3) return;
TileMap newMap = map.resize(-1, Direction.NORTH);
replaceMap(newMap);
}
});
topAll.add(topExpand);
topAll.add(topShrink);
// Note: Maximum size is not enforced, but minimum size is 3x3. No worries: templates that are started at the top left,
// when saved, are always shrunk-to-fit to the samllest possible size removing extra rows and columns to the right and bottom.
// LEFT
JPanel leftAll = new JPanel();
leftAll.setLayout(new BoxLayout(leftAll, BoxLayout.PAGE_AXIS) );
JButton leftExpand = new BasicArrowButton(SwingConstants.WEST);
leftExpand.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) {
TileMap newMap = internalEditor.getTileMap().resize(1, Direction.WEST);
replaceMap(newMap);
}
});
JButton leftShrink = new BasicArrowButton(SwingConstants.EAST);
leftShrink.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) {
TileMap map = internalEditor.getTileMap();
if (map.getColumnCount() <= 3) return;
TileMap newMap = map.resize(-1, Direction.WEST);
replaceMap(newMap);
}
});
leftAll.add(leftExpand);
leftAll.add(leftShrink);
// BOTTOM
JPanel bottomAll = new JPanel();
bottomAll.setLayout(new BoxLayout(bottomAll, BoxLayout.LINE_AXIS) );
JButton bottomExpand = new BasicArrowButton(SwingConstants.SOUTH);
bottomExpand.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) {
TileMap newMap = internalEditor.getTileMap().resize(1, Direction.SOUTH);
replaceMap(newMap);
}
});
JButton bottomShrink = new BasicArrowButton(SwingConstants.NORTH);
bottomShrink.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) {
TileMap map = internalEditor.getTileMap();
if (map.getRowCount() <= 3) return;
TileMap newMap = map.resize(-1, Direction.SOUTH);
replaceMap(newMap);
}
});
bottomAll.add(bottomExpand);
bottomAll.add(bottomShrink);
// RIGHT
JPanel rightAll = new JPanel();
rightAll.setLayout(new BoxLayout(rightAll, BoxLayout.PAGE_AXIS) );
JButton rightExpand = new BasicArrowButton(SwingConstants.EAST);
rightExpand.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) {
TileMap newMap = internalEditor.getTileMap().resize(1, Direction.EAST);
replaceMap(newMap);
}
});
JButton rightShrink = new BasicArrowButton(SwingConstants.WEST);
rightShrink.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) {
TileMap map = internalEditor.getTileMap();
if (map.getColumnCount() <= 3) return;
TileMap newMap = map.resize(-1, Direction.EAST);
replaceMap(newMap);
}
});
rightAll.add(rightExpand);
rightAll.add(rightShrink);
primaryEditor.add(topAll, BorderLayout.NORTH);
primaryEditor.add(leftAll, BorderLayout.WEST);
primaryEditor.add(bottomAll, BorderLayout.SOUTH);
primaryEditor.add(rightAll, BorderLayout.EAST);
// Controls:
saveButton = new JButton("");
saveButton.addActionListener(new ActionListener() {
@Override public void actionPerformed(ActionEvent arg0) {
saveAction.apply(new TemplatePair(baseTemplate, Template.fromTileMap(internalEditor.getTileMap() ) ) );
}
});
add(saveButton);
updateSaveButtonText();
}
/**
*
* Updates the text of the save button to either show 'save' for new templates or 'overwrite' for existing ones.
* This must be called after the button is constructed, and subsequently each time 'baseTemplate' is modified.
*
*/
private void updateSaveButtonText() {
saveButton.setText( baseTemplate != null
? "Overwrite"
: "Save");
}
private void setBaseTemplate(Template t) {
baseTemplate = t;
updateSaveButtonText();
}
/**
*
* Creates the template editor with no initial template (editor starts as 3x3 grid with nothing defined)
*
* @param world
* the world the template is created for
*
* @param saveAction
* called when the template is 'saved'. Passes both the original template and the template that was saved. The original
* can be used to determine which template to 'replace' for edit operations. It is possible for base template to be null
* if this editor was in a 'new template' state and not a 'modifying template' state.
*
*/
public TemplateEditor(World world, Function<TemplatePair, Void> saveAction) {
this(null, world, saveAction);
}
/**
*
* Replaces the template currently being editing with the given template. Any changes to the
* current template are discarded.
*
* @param t
* the template to now be editing.
*
*/
public void replaceTemplate(Template t) {
setBaseTemplate(t);
replaceMap(t.fitToTilemap() );
}
/**
*
* Removes the current template from the editor, replacing the editor with the standard 3x3
* tilemap editor and no previous template.
*
*/
public void clearTemplate() {
setBaseTemplate(null);
replaceMap(new TileMap(3, 3) );
}
/**
*
* Attempts to set the brush for this editor's underlying map editor according to the paintbrush type and id. If that is not
* possible, this method does nothing.
* <p/>
* This method is intended to sync with the level editor when new brushes are chosen.
*
* @param brush
*/
public void trySetTileIdAndBrush(PaintbrushType brush, int id) {
if (MapEditor.isPaintbrushToTilebrush(brush) ) {
internalEditor.setBrushAndId(MapEditor.paintbrushToTilebrush(brush), id);
}
}
/**
*
* Enumerates a pairing of the original template that an editor was created with (or
*
* @author Erika Redmark
*
*/
public static class TemplatePair {
public final Template base;
public final Template modified;
public TemplatePair(final Template base, final Template modified) {
this.base = base;
this.modified = modified;
}
}
/**
*
* Replaces the current map editor with an editor for the new map, automatically handling removing the component
* from the view and replacing it, and then resizing all relevant components.
*
* @param newMap
* the new map to be editing. Typically a resized version of the older one
*
*/
private void replaceMap(final TileMap newMap) {
Background background = internalEditor.getMapBackground();
World world = internalEditor.getWorld();
primaryEditor.remove(internalEditor);
internalEditor = new MapEditor(newMap, background, world, true);
primaryEditor.add(internalEditor, BorderLayout.CENTER);
getParent().revalidate();
getParent().repaint();
}
// Modified whenever tilemap is resized.
private MapEditor internalEditor;
// Sent during a save operation to the appropriate callbacks
private Template baseTemplate;
// Saved because the save button will be 'save' for new templates and 'overwrite' for editing existing ones.
private final JButton saveButton;
// When replacing a tileMap, it must be added to the primary editor only.
private final JPanel primaryEditor;
}